package EA.testEAs;
import EA.*;
import EA.testproblems.*;
import EA.operators.selection.*;
import EA.recording.*;
import java.util.Hashtable;
import RKUjava.datastructures.RKUVector;
import RKUjava.util.RKUValuation;
import RKUjava.lang.RKUCloneable;
import Visualization.plot.*;

/** Implements a tagging GA. It is possible to run the GA with either bit 
    representation or realvector representation.
    The following is a simple implementation of the main class.<br>
    <pre>
    import EA.*;
    import EA.testproblems.*;
    import EA.testEAs.TaggingGA;
    
    public class taggingga
    {
      &nbsp;&nbsp;static NumericalProblem curproblem;
      &nbsp;&nbsp;static TaggingGA sga;
      &nbsp;&nbsp;static BatchJobScheduler BJS;
    
      &nbsp;&nbsp;public static void main(String argv[])
        &nbsp;&nbsp;{
	  &nbsp;&nbsp;&nbsp;&nbsp;if (argv.length==0){
	    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println((new TaggingGA()).getDefaultSettings());
	    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(0);
	  &nbsp;&nbsp;&nbsp;&nbsp;}
	  &nbsp;&nbsp;&nbsp;&nbsp;curproblem = new UrsemMultimodal1();
  	  &nbsp;&nbsp;&nbsp;&nbsp;sga = new TaggingGA(curproblem);
	  &nbsp;&nbsp;&nbsp;&nbsp;BJS = new BatchJobScheduler(sga);
	  &nbsp;&nbsp;&nbsp;&nbsp;BJS.startScheduler(argv);
	  &nbsp;&nbsp;&nbsp;&nbsp;System.exit(0);
        &nbsp;&nbsp;}
    }</pre>

    @see EA.BitNumericalGenome
    @see EA.RealNumericalGenome
*/
public class TaggingGA implements BatchJobEA
{

  /** Representation type. (Default = 0)<br>
      0 = Realvalue vectors with RealNumericalGenome.<br>
      1 = Bitrepresented vectors with BitNumericalGenome.<br>
  */
  public int representationtype = 0;

  /** The current problem. */
  public NumericalProblem curproblem;

  /** The current framework. */
  public NumericalFramework nf;
    
  /** Create a standard GA.<br><b>Note:</b> Remember to set curproblem. */
  public TaggingGA()
    {
      super();
    }

  /** Create a standard GA with the given problem. */
  public TaggingGA(NumericalProblem aproblem)
    {
      super();
      curproblem = aproblem;
    }

  // **** BatchJob implementation *****************************

  /** Set the settings for the algorithm. The passed hashtable contains
      only String objects which are all related to the input file given 
      to the scheduler. 
  */
  public void setSettings(Hashtable settings)
    {
      String curstr;

      if ((curstr = (String)settings.get("representationtype"))!=null) {
	representationtype = (new Integer(curstr)).intValue();
      }

      switch (representationtype) {
      case 0:
	nf = new RealTaggingGAFramework(curproblem);
	((RealTaggingGAFramework)nf).theGA = this;
	break;
      case 1:
	break;
      }
      ((BatchJobEA)nf).setSettings(settings);

    };

  /** Get the default settings in the BatchJobScheduler format. This 
      method can be used to reconstruct an input file for the scheduler. 
  */
  public String getDefaultSettings()
    {
      RealTaggingGAFramework f = new RealTaggingGAFramework();
      StringBuffer res = new StringBuffer();

      res.append(BatchJobScheduler.getDefaultSettings());
      res.append("#################################################################\n");
      res.append("# ------------- Algorithmic settings ----------------------------\n");
      res.append("\n");
      res.append("# Print information to stdout (true/false)\n");
      res.append("verbose = "+f.verbose+"\n");
      res.append("\n");
      res.append("# Representation type\n");
      res.append("#  0 = Real valued vectors (RealNumericalGenome).\n");
      res.append("#  1 = Binary encoded vectors (BitNumericalGenome).\n");
      res.append("representationtype = "+representationtype+"\n");
      res.append("\n");
      res.append("# Probability for mutation\n");
      res.append("p_m = "+f.p_m+"\n");
      res.append("\n");
      res.append("# Probability for mutation of a tag bit\n");
      res.append("p_mtag = "+f.p_mtag+"\n");
      res.append("\n");
      res.append("# Probability for crossover\n");
      res.append("p_c = "+f.p_c+"\n");
      res.append("\n");
      res.append("# Maximal number of iterations\n");
      res.append("maxiterations = "+f.maxiterations+"\n");
      res.append("\n");
      res.append("# Initial number of individuals\n");
      res.append("initialpopsize = "+f.initialpopsize+"\n");
      res.append("\n");
      res.append("# Number of species\n");
      res.append("numberofspecies = "+f.numberofspecies+"\n");
      res.append("\n");
      res.append("# Display graph of fitness and mean for each run.\n");
      res.append("# 0 = Don't display graph.\n");
      res.append("# 1 = Display graph of fitness & mean.\n");
      res.append("displaygraph = 1\n\n");
      res.append("# Display trajectory graph of best individual's position.\n");
      res.append("# 0 = Don't display graph.\n");
      res.append("# 1 = Display graph of position.\n");
      res.append("displaybest = 1\n\n");

      return res.toString();
    };

  /** Initialize a batchjob. This is called once before any run is performed.
   */
  public void initializeBatchJobs()
    {};

  /** Finalize a batchjob. This is called once after all runs are performed.
   */
  public void finalizeBatchJobs()
    {
      ((BatchJobEA)nf).finalizeBatchJobs();
    };

  /** Initialize a run series. This is called once before the specified 
      number of runs are performed. (BJSruns in the batchfile)
  */
  public void initializeRunSeries()
    {
      ((BatchJobEA)nf).initializeRunSeries();
    };

  /** Finalize a run series. This is called once after the specified 
      number of runs are performed. (BJSruns in the batchfile)
  */
  public void finalizeRunSeries()
    {
      ((BatchJobEA)nf).finalizeRunSeries();
    };

  /** Initialize and start a single run of the algorithm. You can 
      usually just call doEA() from your framework.
  */
  public void initializeAndStartRun()
    {
      ((BatchJobEA)nf).initializeAndStartRun();
    };

  /** Finalize the single run of the algorithm. This is called immediately
      after the initializeAndStartRun(). It can be used to reset 
      measurements, increase counters, etc.
  */
  public void finalizeRun()
    {
      ((BatchJobEA)nf).finalizeRun();
    };

  /** Get the run data as text. Your implementation must return
      an array of objects that each implements the interface ConvertToText from the package
      EA.recording. You can use the classes found in EA.recording or 
      implement your own classes.
      @see EA.recording.ConvertToText
  */
  public Object[] getDataAsText()
    {
      return ((BatchJobEA)nf).getDataAsText();
    };
}

class RealTaggingGAFramework extends RealNumericalFramework implements BatchJobEA, DataPlot2DExtraction
{

  // True if the info should be printed to stdout
  boolean verbose;
    
  // Probability for mutation
  public double p_m = 0.05;
    
  // Probability for crossover
  public double p_c = 0.9;

  // Maximal number of iterations
  public int maxiterations = 200;
    
  // Initial number of individuals
  public int initialpopsize = 100;

  // Is this problem a maximization problem.
  public boolean ismaximization = true;

  public boolean isdynamicproblem = false;

  // Number of species 
  public int numberofspecies = 2;

  // Probablity to mutate the species
  public double p_mtag = 0.2;

  public NumericalProblem curproblem;

  public TaggingGAIndividual[] population;

  public TaggingGA theGA;

  public int displaygraph = 0, displaybest = 0;

  DataPlot2D curplotter, curpeakplotter;

  private SequenceTable results1;

  RKUVector[] subpopulations;

  /** Create a new framework. */
  public RealTaggingGAFramework()
    {
      super();
    }

  /** Create a new framework. */
  public RealTaggingGAFramework(NumericalProblem numproblem)
    {
      super(numproblem);
      curproblem = numproblem;
      ismaximization = curproblem.ismaximization;
      curplotter = new DataPlot2D(this, 0);
      curpeakplotter = new DataPlot2D(this, 1);

      if (curproblem.dimensions==2) {
	curpeakplotter.setLowerX(curproblem.intervals[0].min);
	curpeakplotter.setUpperX(curproblem.intervals[0].max);
	curpeakplotter.setLowerY(curproblem.intervals[1].min);
	curpeakplotter.setUpperY(curproblem.intervals[1].max);
      }

    }

  public void Framework_doEA_inner()
    {
      statistics();
      recombine();
      alter();
      selection();
      evaluate();

      if (isdynamicproblem)
	((DynamicNumericalProblem)curproblem).updateProblem();
    }

  int specieswidth;
  int prewidth;

  public void statistics() 
    {
      TaggingGAIndividual best = null, cur;
      int i,j;

      for (i=0;i<initialpopsize;i++) {
	if (best == null)
	  best = population[i];
	else {
	  if ((ismaximization && best.getFitness()<population[i].getFitness()) || (!ismaximization && best.getFitness()>population[i].getFitness()))
	    best = population[i];
	}
      }

      results1.initializeRow();
      results1.updateLastRow(0,iteration);
      results1.updateLastRow(1,best.getFitness());
      results1.updateLastRow(2,best.getPos());

      if (verbose)
	System.out.println("Bestfit="+best.getFitness());
	
      for (i=0;i<numberofspecies;i++) {
	best = null;
	for (j=0;j<subpopulations[i].size();j++) {
	  cur = (TaggingGAIndividual)subpopulations[i].elementAt(j);
	  if (best == null)
	    best = cur;
	  else {
	    if ((ismaximization && best.getFitness()<cur.getFitness()) || (!ismaximization && best.getFitness()>cur.getFitness()))
	      best = cur;
	  }
	}

	results1.updateLastRow(0+prewidth+i*specieswidth,subpopulations[i].size());
	if (best!=null) {
	  results1.updateLastRow(1+prewidth+i*specieswidth,best.getFitness());
	  results1.updateLastRow(2+prewidth+i*specieswidth,best.getPos());
	  results1.updateLastRow(2+prewidth+i*specieswidth+curproblem.dimensions, PopulationMeasurements.calcMean(population));
	  results1.updateLastRow(3+prewidth+i*specieswidth+curproblem.dimensions, PopulationMeasurements.calcStandardDeviation(population));

	}
      }
    }

  public boolean terminate()
    {
      return iteration<maxiterations-1;
    }

  public void Framework_initialize_inner()
    {
      int i;
      population = new TaggingGAIndividual[initialpopsize];

      for (i = 0;i<initialpopsize;i++) {
	population[i] = new TaggingGAIndividual(this, true, theGA.representationtype);
      };
      evaluate();

      results1 = new SequenceTable("taggingGA");
      results1.addColumn(new SequenceTableColumn("Generation"));
      results1.addColumn(new SequenceTableColumn("Bestfit"));
      results1.addColumns(curproblem.getSequenceTableColumns(""));

      subpopulations = new RKUVector[numberofspecies];
      for (i=0;i<numberofspecies;i++) {
	subpopulations[i] = new RKUVector();

	results1.addColumn(new SequenceTableColumn("SP"+i+": size"));
	results1.addColumn(new SequenceTableColumn("SP"+i+": Bestfit"));
	results1.addColumns(curproblem.getSequenceTableColumns("SP"+i+": "));
	results1.addColumn(new SequenceTableColumn("SP"+i+": Mean"));
	results1.addColumn(new SequenceTableColumn("SP"+i+": Std deviation"));
      }

      specieswidth = 4 + curproblem.dimensions;
      prewidth = 2 + curproblem.dimensions;

      updatesubpopulations();

      if (isdynamicproblem)
	((DynamicNumericalProblem)curproblem).resetProblem();
    };

  /** BETA style inner call for summarize().
   */
  public void Framework_summarize_inner()
    {
      statistics();
    }

  /** Update the subpopulations. */
  public void updatesubpopulations()
    {
      int i;

      for (i = 0;i<numberofspecies;i++) {
	subpopulations[i].removeAllElements();
      }

      for (i = 0;i<initialpopsize;i++) {
	subpopulations[population[i].currentspecies].addElement(population[i]);
      }
    }

  RKUVector tmppop = new RKUVector();

  /** Override this in your implementation. */
  public void selection()
    {
      int i;
      Object[] selected;

      tmppop.removeAllElements();

      for (i=0;i<numberofspecies;i++) {
	selected = TournamentSelection.select(subpopulations[i].getElementData(), subpopulations[i].size(), ismaximization, true, 0, subpopulations[i].size());
	tmppop.addArray(selected);
	subpopulations[i].replaceDataArray(selected);
      };

      tmppop.copyInto(population);
    };

  TaggingGAIndividual[] parents = new TaggingGAIndividual[2];

  /* Perform crossover on nations. */
  public void recombine()
    {
      int i, j;
      RKUVector tmpsubpopulation;

      tmppop.removeAllElements();
      for (i = 0;i<numberofspecies;i++) {
	tmpsubpopulation = (RKUVector)subpopulations[i].clone();
	for (j=0;j<tmpsubpopulation.size();j++) {
	  if (UsefulRoutines.randomBiasedBoolean(p_c)) {
	    RandomSelection.selectInto(tmpsubpopulation.getElementData(), 2, ismaximization, false, 0, tmpsubpopulation.size(), parents);
	    tmpsubpopulation.setElementAt(parents[0].crossover(parents[0], parents[1]), j);
	  }
	}
	tmppop.addVector(tmpsubpopulation);
      }

      tmppop.copyInto(population);
    }

  /** Mutate all individuals. */
  public void alter()
    {
      int i;

      for (i = 0; i<initialpopsize;i++) {
	population[i].mutate(p_m);
      }

      updatesubpopulations();
    }

  /** Evaluate all individuals. */
  public void evaluate()
    {
      double fitsum;
      int i;

      fitsum = 0;

      for (i = 0; i<initialpopsize;i++) {
	fitsum += population[i].getFitness();
      }
      //	System.out.println("Fitsum = "+fitsum);
    }

  // **** BatchJob implementation *****************************

  /** Set the settings for the algorithm. The passed hashtable contains
      only String objects which are all related to the input file given 
      to the scheduler. 
  */
  public void setSettings(Hashtable settings)
    {
      String curstr;

      if ((curstr = (String)settings.get("verbose"))!=null) {
	verbose = (new Boolean(curstr)).booleanValue();
      }

      if( (curstr = (String)settings.get("p_m"))!=null) {
	p_m = (new Double(curstr)).doubleValue();
      }

      if( (curstr = (String)settings.get("p_c"))!=null) {
	p_c = (new Double(curstr)).doubleValue();
      }

      if ((curstr = (String)settings.get("maxiterations"))!=null) {
	maxiterations = (new Integer(curstr)).intValue();
      }

      if ((curstr = (String)settings.get("initialpopsize"))!=null) {
	initialpopsize = (new Integer(curstr)).intValue();
      }

      if( (curstr = (String)settings.get("p_mtag"))!=null) {
	p_mtag = (new Double(curstr)).doubleValue();
      }

      if ((curstr = (String)settings.get("numberofspecies"))!=null) {
	numberofspecies = (new Integer(curstr)).intValue();
      }

      if ((curstr = (String)settings.get("displaygraph"))!=null) {
	displaygraph = (new Integer(curstr)).intValue();
      }

      if (curproblem.dimensions==2 && ((curstr = (String)settings.get("displaybest"))!=null)) {
	displaybest = (new Integer(curstr)).intValue();
      }

      if (curproblem instanceof DynamicNumericalProblem) {
	isdynamicproblem = true;
	((DynamicNumericalProblem)curproblem).setSettings(settings, this);
      }
      else
	isdynamicproblem = false;
    };

  /** Initialize a batchjob. This is called once before any run is performed.
   */
  public void initializeBatchJobs()
    {
    };

  /** Finalize a batchjob. This is called once after all runs are performed.
   */
  public void finalizeBatchJobs()
    {
    };

  /** Get the default settings in the BatchJobScheduler format. This 
      method can be used to reconstruct an input file for the scheduler. 
  */
  public String getDefaultSettings()
    {
      return "";
    };

  /** Initialize a run series. This is called once before the specified 
      number of runs are performed. (BJSruns in the batchfile)
  */
  public void initializeRunSeries()
    {};

  /** Finalize a run series. This is called once after the specified 
      number of runs are performed. (BJSruns in the batchfile)
  */
  public void finalizeRunSeries()
    {};

  /** Initialize and start a single run of the algorithm. You can 
      usually just call doEA() from your framework.
  */
  public void initializeAndStartRun()
    {
      doEA();
    };

  /** Finalize the single run of the algorithm. This is called immediately
      after the initializeAndStartRun(). It can be used to reset 
      measurements, increase counters, etc.
  */
  public void finalizeRun()
    {
      switch(displaygraph){ 
      case 0: // Do nothing
	break;
      case 1:
	curplotter.updatePlot();
	break;
      }

      switch(displaybest){ 
      case 0: // Do nothing
	break;
      case 1:
	curpeakplotter.updatePlot();
	break;
      }
    };

  /** Get the run data as text. Your implementation must return
      an array of objects that each implements the interface ConvertToText from the package
      EA.recording. You can use the classes found in EA.recording or 
      implement your own classes.
      @see EA.recording.ResultSet
      @see EA.recording.Measurement
      @see EA.recording.ConvertToText
  */
  public Object[] getDataAsText()
    {
      Object[] res = new Object[1];

      res[0] = results1;
      return res;
    };


  // ************* Plot interface **********************************

  int[] mappings;

  int[][] resultstypes = new int[0][0];
  int[][] trajectorytypes = new int[0][0];

  /** Extract the data as a 2 dimensional array of objects. <br>
      <b>Note:</b> All second-indexes arrays must have equal length.
  */
  public Object[][] extract2DAsObjects(int plotternumber)
    {
      Object[][] res = new Object[0][0];
      int i;

      switch (plotternumber) {
      case 0:
	res = new Object[numberofspecies*2+1][0];
	resultstypes = new int[numberofspecies*2+1][2];
	res[0] = (results1.getColumn(1)).getDataAsArray();
	resultstypes[0][0] = 0;
	resultstypes[0][1] = 0;
	for (i=0;i<numberofspecies;i++) {
	  res[i*2+1] = (results1.getColumn(1+prewidth+i*specieswidth)).getDataAsArray();
	  res[i*2+2] = (results1.getColumn(2+prewidth+i*specieswidth+curproblem.dimensions)).getDataAsArray();
	  resultstypes[i*2+1][0] = 0;
	  resultstypes[i*2+1][1] = 0;
	  resultstypes[i*2+2][0] = 0;
	  resultstypes[i*2+2][1] = 0;

	}
	break;
      case 1:
	res = new Object[numberofspecies*2][0];
	trajectorytypes = new int[numberofspecies*2][2];
	for (i=0;i<numberofspecies;i++) {
	  res[i*2] = (results1.getColumn(2+prewidth+i*specieswidth)).getDataAsArray();
	  res[i*2+1] = (results1.getColumn(3+prewidth+i*specieswidth)).getDataAsArray();
	  trajectorytypes[i*2][0] = 2;
	  trajectorytypes[i*2][1] = 0;
	  trajectorytypes[i*2+1][0] = 2;
	  trajectorytypes[i*2+1][1] = 0;
	}
	break;
      }
      return res;
    };

  /** Extract the data as a 2 dimensional array of doubles.<br>
      <b>Note:</b> All second-indexes arrays must have equal length. */
  public double[][] extract2DAsDoubles(int plotternumber)
    {
      return null;
    };

  public int[][] extract2DDataTypes(int plotternumber)
    {
      switch(plotternumber)
	{
	case 0:
	  return resultstypes;
	case 1: 
	  return trajectorytypes;
	}
      return resultstypes;
    };

  /** Extract the name of a graph given an index. */
  public String extract2DName(int index, int plotternumber)
    {
      switch (plotternumber) {
      case 0:
	switch(index) {
	case 0:
	  return "Bestfit (all)";
	default:
	  if ((index%2)==1)
	    return "Bestfit ("+(index/2)+")";
	  else
	    return "Mean ("+(index/2-1)+")";
	}
      case 1:
	return "Bestpos ("+index+")";
      }
      return "";
    };
}

class TaggingGAIndividual extends Individual implements RKUCloneable, RKUValuation, NumericalInspector
{

  public int currentspecies = 0;

  /** Create a new TaggingGAIndividual. 
      @param frame The framework for this individual.
      @param initialize True if you want to create a new Bit/Real-NumericalGenome.
      @param reptype The type of representation.
  */
  public TaggingGAIndividual(NumericalFramework frame, boolean initialize, int reptype)
    {
      super(frame);

      if (initialize) {
	switch(reptype) {
	case 0:
	  genome = new RealNumericalGenome((RealNumericalFramework)frame,true);
	  currentspecies = UsefulRoutines.randomInt(((RealTaggingGAFramework)frame).numberofspecies);
	  break;
	case 1:
	  genome = new BitNumericalGenome((BitNumericalFramework)frame,true);
	  break;
	}
      }
    }

  public static TaggingGAIndividual crossover(TaggingGAIndividual individual1, TaggingGAIndividual individual2)
    {
      TaggingGAIndividual res;

      res = new TaggingGAIndividual((NumericalFramework)individual1.framework, false, 0);
      res.currentspecies = individual1.currentspecies;

      if (individual1.genome instanceof RealNumericalGenome) {
	res.genome = RealNumericalGenome.crossover(individual1.genome, individual2.genome);
      }
      else if (individual1.genome instanceof BitNumericalGenome) {
	res.genome = BitNumericalGenome.crossover(individual1.genome, individual2.genome);
      };
      return res;
    };

  public void mutate(double p_m)
    {
      int mutationnumber = 0;
      int bitcnt = 1;
      int numberofspecies;
      double p_mtag;

      super.mutate(p_m);

      if (framework instanceof RealTaggingGAFramework) {
	p_mtag = ((RealTaggingGAFramework)framework).p_mtag;
	numberofspecies = ((RealTaggingGAFramework)framework).numberofspecies;
	while (bitcnt<numberofspecies) {
	  if (UsefulRoutines.randomBiasedBoolean(p_mtag)) {
	    mutationnumber += bitcnt;
	  }
	  bitcnt *= 2;
	}

	if (mutationnumber>numberofspecies)
	  mutationnumber -= (bitcnt/2);

	currentspecies = currentspecies ^ mutationnumber;
      }
    }
    

  public double getFitness()
    {
      return ((NumericalFitness)((NumericalFramework)framework).fitnessfunction).calcFitness(((NumericalGenome)genome).getPos());
    }

  public Object clone()
    {
      TaggingGAIndividual res;

      res = new TaggingGAIndividual((NumericalFramework)framework, false, 0);
      res.genome = (Genome)(this.genome.clone());
      return res;
    }

  public double value()
    {
      return this.getFitness();
    }

  public double[] getPos()
    {
      return ((NumericalGenome)genome).getPos();
    }

  // Inteface-implementation: NumericalInspector
  public double[] inspectGetPos() { return getPos(); }
  public double inspectGetFitness() { return getFitness(); }
}
